/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.datasource.pooled;

import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.logging.Logger;

import javax.sql.DataSource;

import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

/**
 * This is a simple, synchronous, thread-safe database connection pool.
 *
 * @author Clinton Begin
 */
/**
 * 有连接池的数据源 这个是mybatis自己实行了一个连接池
 * 其他还有诸如C3P0的com.mchange.v2.c3p0.ComboPooledDataSource DBCP 等等 还有一些人喜欢自己用apache
 * commons pool写一个连接池
 */
public class PooledDataSource implements DataSource {

	private static final Log log = LogFactory.getLog(PooledDataSource.class);

	/**
	 * 有一个池状态
	 */
	private final PoolState state = new PoolState(this);

	/**
	 * 里面有一个UnpooledDataSource
	 */
	private final UnpooledDataSource dataSource;

	// OPTIONAL CONFIGURATION FIELDS
	/**
	 * 最大活动连接数
	 */
	protected int poolMaximumActiveConnections = 10;
	/**
	 * 最大空闲连接数
	 */
	protected int poolMaximumIdleConnections = 5;
	/**
	 * 最大可回收时间
	 */
	protected int poolMaximumCheckoutTime = 20000;
	/**
	 * 没有连接时，重尝试获取连接以及打印日志的时间间隔
	 */
	protected int poolTimeToWait = 20000;
	/**
	 * 检查连接正确的语句，默认为"NO PING QUERY SET"，即没有，使用会导致抛异常
	 */
	protected String poolPingQuery = "NO PING QUERY SET";
	/**
	 * 是否开启ping检测
	 */
	protected boolean poolPingEnabled = false;
	/**
	 * 设置ping检测时间间隔，通常用于检测超时连接
	 */
	protected int poolPingConnectionsNotUsedFor = 0;
	/**
	 * 连接的类型编码
	 */
	private int expectedConnectionTypeCode;

	/**
	 * 构造器----创建UnpooledDataSourceo数据源
	 */
	public PooledDataSource() {
		// pooledDataSource
		// 带有unpooledDataSource的实例对象，只为了创建新连接，pooledDataSource可理解为维护了连接池的unpooledDataSource
		dataSource = new UnpooledDataSource();
	}

	/**
	 * 构造器
	 * 
	 * @param driver
	 *            驱动
	 * @param url
	 *            url
	 * @param username
	 *            用户名
	 * @param password
	 *            密码
	 */
	public PooledDataSource(String driver, String url, String username, String password) {
		dataSource = new UnpooledDataSource(driver, url, username, password);
		expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
				dataSource.getPassword());
	}

	/**
	 * 构造器
	 * 
	 * @param driver
	 *            驱动
	 * @param url
	 *            url
	 * @param driverProperties
	 *            驱动属性
	 */
	public PooledDataSource(String driver, String url, Properties driverProperties) {
		dataSource = new UnpooledDataSource(driver, url, driverProperties);
		expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
				dataSource.getPassword());
	}

	/**
	 * 构造器
	 * 
	 * @param driverClassLoader
	 *            驱动类加载器
	 * @param driver
	 *            驱动
	 * @param url
	 *            url
	 * @param username
	 *            用户名
	 * @param password
	 *            密码
	 */
	public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username,
			String password) {
		dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
		expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
				dataSource.getPassword());
	}

	/**
	 * 构造器
	 * 
	 * @param driverClassLoader
	 *            驱动类加载器
	 * @param driver
	 *            驱动
	 * @param url
	 *            url
	 * @param driverProperties
	 *            驱动属性
	 */
	public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
		dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
		expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
				dataSource.getPassword());
	}

	@Override
	public Connection getConnection() throws SQLException {
		// 覆盖了DataSource.getConnection方法，每次都是pop一个Connection，即从池中取出一个来
		return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		return popConnection(username, password).getProxyConnection();
	}

	@Override
	public void setLoginTimeout(int loginTimeout) throws SQLException {
		DriverManager.setLoginTimeout(loginTimeout);
	}

	@Override
	public int getLoginTimeout() throws SQLException {
		return DriverManager.getLoginTimeout();
	}

	@Override
	public void setLogWriter(PrintWriter logWriter) throws SQLException {
		DriverManager.setLogWriter(logWriter);
	}

	@Override
	public PrintWriter getLogWriter() throws SQLException {
		return DriverManager.getLogWriter();
	}

	/**
	 * 设置驱动
	 * 
	 * @param driver
	 */
	public void setDriver(String driver) {
		dataSource.setDriver(driver);
		forceCloseAll();
	}

	/**
	 * 设置url
	 * 
	 * @param url
	 */
	public void setUrl(String url) {
		dataSource.setUrl(url);
		forceCloseAll();
	}

	/**
	 * 设置用户名
	 * 
	 * @param username
	 */
	public void setUsername(String username) {
		dataSource.setUsername(username);
		forceCloseAll();
	}

	/**
	 * 设置密码
	 * 
	 * @param password
	 */
	public void setPassword(String password) {
		dataSource.setPassword(password);
		forceCloseAll();
	}

	/**
	 * 设置是否自动提交
	 * 
	 * @param defaultAutoCommit
	 */
	public void setDefaultAutoCommit(boolean defaultAutoCommit) {
		dataSource.setAutoCommit(defaultAutoCommit);
		forceCloseAll();
	}

	/**
	 * 设置默认的事务隔离级别
	 * 
	 * @param defaultTransactionIsolationLevel
	 */
	public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
		dataSource.setDefaultTransactionIsolationLevel(defaultTransactionIsolationLevel);
		forceCloseAll();
	}

	/**
	 * 设置驱动属性
	 * 
	 * @param driverProps
	 */
	public void setDriverProperties(Properties driverProps) {
		dataSource.setDriverProperties(driverProps);
		forceCloseAll();
	}

	/**
	 * The maximum number of active connections(最大的活跃连接数)
	 *
	 * @param poolMaximumActiveConnections
	 *            The maximum number of active connections
	 */
	public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
		this.poolMaximumActiveConnections = poolMaximumActiveConnections;
		forceCloseAll();
	}

	/**
	 * The maximum number of idle connections(最大空闲连接数)
	 *
	 * @param poolMaximumIdleConnections
	 *            The maximum number of idle connections
	 */
	public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
		this.poolMaximumIdleConnections = poolMaximumIdleConnections;
		forceCloseAll();
	}

	/**
	 * The maximum time a connection can be used before it *may* be given away
	 * again.(一个连接可以被重新使用的)
	 *
	 * @param poolMaximumCheckoutTime
	 *            The maximum time
	 */
	public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
		this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
		forceCloseAll();
	}

	/**
	 * The time to wait before retrying to get a connection(设置重新尝试获得连接之前等待的时间)
	 *
	 * @param poolTimeToWait
	 *            The time to wait
	 */
	public void setPoolTimeToWait(int poolTimeToWait) {
		this.poolTimeToWait = poolTimeToWait;
		forceCloseAll();
	}

	/**
	 * The query to be used to check a connection（设置检查连接的查询）
	 *
	 * @param poolPingQuery
	 *            The query
	 */
	public void setPoolPingQuery(String poolPingQuery) {
		this.poolPingQuery = poolPingQuery;
		forceCloseAll();
	}

	/**
	 * Determines if the ping query should be used.(设置是否应该使用ping查询。)
	 *
	 * @param poolPingEnabled
	 *            True if we need to check a connection before using it
	 */
	public void setPoolPingEnabled(boolean poolPingEnabled) {
		this.poolPingEnabled = poolPingEnabled;
		forceCloseAll();
	}

	/**
	 * If a connection has not been used in this many milliseconds, ping the
	 * database to make sure the connection is still
	 * good.(设置在这段时间内没有使用连接，ping该数据库以确保连接仍然良好。)
	 *
	 * @param milliseconds
	 *            the number of milliseconds of inactivity that will trigger a
	 *            ping
	 */
	public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
		this.poolPingConnectionsNotUsedFor = milliseconds;
		forceCloseAll();
	}

	/**
	 * 得到驱动
	 * 
	 * @return
	 */
	public String getDriver() {
		return dataSource.getDriver();
	}

	/**
	 * 得到URL
	 * 
	 * @return
	 */
	public String getUrl() {
		return dataSource.getUrl();
	}

	/**
	 * 得到用户名
	 * 
	 * @return
	 */
	public String getUsername() {
		return dataSource.getUsername();
	}

	/**
	 * 得到密码
	 * 
	 * @return
	 */
	public String getPassword() {
		return dataSource.getPassword();
	}

	/**
	 * 是否自动提交
	 * 
	 * @return
	 */
	public boolean isAutoCommit() {
		return dataSource.isAutoCommit();
	}

	/**
	 * 得到 默认的事务隔离级别
	 * 
	 * @return
	 */
	public Integer getDefaultTransactionIsolationLevel() {
		return dataSource.getDefaultTransactionIsolationLevel();
	}

	/**
	 * 得到驱动属性
	 * 
	 * @return
	 */
	public Properties getDriverProperties() {
		return dataSource.getDriverProperties();
	}

	/**
	 * 得到正在使用连接的数量
	 * 
	 * @return
	 */
	public int getPoolMaximumActiveConnections() {
		return poolMaximumActiveConnections;
	}

	/**
	 * 得到空闲连接数
	 */
	public int getPoolMaximumIdleConnections() {
		return poolMaximumIdleConnections;
	}

	/**
	 * 得到在被强制返回之前,池中连接被检查的时间
	 * 
	 */
	public int getPoolMaximumCheckoutTime() {
		return poolMaximumCheckoutTime;
	}

	/**
	 * 得到重新尝试获得连接之前等待的时间
	 */
	public int getPoolTimeToWait() {
		return poolTimeToWait;
	}

	/**
	 * 得到检查连接的查询
	 */
	public String getPoolPingQuery() {
		return poolPingQuery;
	}

	/**
	 * 得到开启或禁用侦测查询
	 */
	public boolean isPoolPingEnabled() {
		return poolPingEnabled;
	}

	/**
	 * 得到用来配置 poolPingQuery 多次时间被用一次
	 */
	public int getPoolPingConnectionsNotUsedFor() {
		return poolPingConnectionsNotUsedFor;
	}

	/**
	 * Closes all active and idle connections in the pool(关闭池中的所有活动和空闲连接)
	 */
	public void forceCloseAll() {
		synchronized (state) {
			expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(),
					dataSource.getPassword());
			// 关闭所有的activeConnections和idleConnections
			for (int i = state.activeConnections.size(); i > 0; i--) {
				try {
					PooledConnection conn = state.activeConnections.remove(i - 1);
					conn.invalidate();

					Connection realConn = conn.getRealConnection();
					if (!realConn.getAutoCommit()) {
						realConn.rollback();
					}
					realConn.close();
				} catch (Exception e) {
					// ignore
				}
			}
			for (int i = state.idleConnections.size(); i > 0; i--) {
				try {
					PooledConnection conn = state.idleConnections.remove(i - 1);
					conn.invalidate();

					Connection realConn = conn.getRealConnection();
					if (!realConn.getAutoCommit()) {
						realConn.rollback();
					}
					realConn.close();
				} catch (Exception e) {
					// ignore
				}
			}
		}
		if (log.isDebugEnabled()) {
			log.debug("PooledDataSource forcefully closed/removed all connections.");
		}
	}

	/**
	 * 得到池状态
	 * 
	 * @return
	 */
	public PoolState getPoolState() {
		return state;
	}

	/**
	 * 收集连接类型code
	 * 
	 * @param url
	 * @param username
	 * @param password
	 * @return
	 */
	private int assembleConnectionTypeCode(String url, String username, String password) {
		return ("" + url + username + password).hashCode();
	}

	/**
	 * 增加连接
	 * 
	 * @param conn
	 * @throws SQLException
	 */
	protected void pushConnection(PooledConnection conn) throws SQLException {

		synchronized (state) {
			// 从活动链表中移除当前连接
			state.activeConnections.remove(conn);
			if (conn.isValid()) {
				// 当前连接有效的话判断是否达到了最大空闲连接数，以及当前的连接是否变更过（即用户名，密码，Url等变更）
				if (state.idleConnections.size() < poolMaximumIdleConnections
						&& conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
					// 统计使用连接时长
					state.accumulatedCheckoutTime += conn.getCheckoutTime();
					if (!conn.getRealConnection().getAutoCommit()) {
						// 没有自动提交的话，先回滚，防止影响下一次使用
						conn.getRealConnection().rollback();
					}
					// 重新创建一个代理连接来封装，可以使得当前使用的连接不会被原有的代理连接影响
					PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
					// 放回空闲链表中
					state.idleConnections.add(newConn);
					// 将原有的代理连接设置为无效
					newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
					// 通知等待获取连接的线程（不去判断是否真的有线程在等待）
					newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
					conn.invalidate();
					if (log.isDebugEnabled()) {
						log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
					}
					// 通知其他线程可以来抢connection了
					state.notifyAll();
				} else {
					// 否则，即空闲的连接已经足够了
					state.accumulatedCheckoutTime += conn.getCheckoutTime();
					if (!conn.getRealConnection().getAutoCommit()) {
						conn.getRealConnection().rollback();
					}
					// 超出空闲连接限制，则直接释放当前连接
					conn.getRealConnection().close();
					if (log.isDebugEnabled()) {
						log.debug("Closed connection " + conn.getRealHashCode() + ".");
					}
					// 将原有的代理连接设置为无效
					conn.invalidate();
				}
			} else {
				if (log.isDebugEnabled()) {
					log.debug("A bad connection (" + conn.getRealHashCode()
							+ ") attempted to return to the pool, discarding connection.");
				}
				// 连接无效，则统计无效连接个数
				state.badConnectionCount++;
			}
		}
	}

	/**
	 * 弹出连接
	 * 
	 * @param username
	 * @param password
	 * @return
	 * @throws SQLException
	 */
	private PooledConnection popConnection(String username, String password) throws SQLException {
		boolean countedWait = false;
		PooledConnection conn = null;
		long t = System.currentTimeMillis();
		int localBadConnectionCount = 0;

		// 最外面是while死循环，如果一直拿不到connection，则不断尝试
		while (conn == null) {
			// 加锁访问
			synchronized (state) {
				if (!state.idleConnections.isEmpty()) {
					// 有空闲连接，直接获取
					conn = state.idleConnections.remove(0);
					if (log.isDebugEnabled()) {
						log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
					}
				} else {
					// 空闲连接不足
					if (state.activeConnections.size() < poolMaximumActiveConnections) {
						// 小于最大活动连接数，直接建立新的连接，并封装代理
						conn = new PooledConnection(dataSource.getConnection(), this);
						if (log.isDebugEnabled()) {
							log.debug("Created connection " + conn.getRealHashCode() + ".");
						}
					} else {
						// 超出最大活动连接数，不能创建连接
						PooledConnection oldestActiveConnection = state.activeConnections.get(0);
						// 获取使用时间最长的活动连接，并计算使用的时间
						long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
						if (longestCheckoutTime > poolMaximumCheckoutTime) {
							// 超出了最大可回收时间，直接回收该连接，回收过期次数增加
							state.claimedOverdueConnectionCount++;
							// 统计过期回收时间增加
							state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
							// 统计使用时间增加
							state.accumulatedCheckoutTime += longestCheckoutTime;
							// 将连接从活动队列中移除
							state.activeConnections.remove(oldestActiveConnection);
							if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
								// 如果不是自动提交事务，则将其回滚，因为可能存在一些操作
								oldestActiveConnection.getRealConnection().rollback();
							}
							// 删掉最老的连接，然后再new一个新连接
							// 使用新的代理封装，可以使得不会被原有的影响
							conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
							// 将之前的代理设置为无效
							oldestActiveConnection.invalidate();
							if (log.isDebugEnabled()) {
								log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
							}
						} else {
							// 如果checkout时间不够长，等待吧
							// Must wait
							try {
								if (!countedWait) {
									// 增加获取连接需要等待的次数
									state.hadToWaitCount++;
									countedWait = true;
								}
								if (log.isDebugEnabled()) {
									log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
								}
								long wt = System.currentTimeMillis();
								// 等待
								state.wait(poolTimeToWait);
								// 增加获取连接的等待时间
								state.accumulatedWaitTime += System.currentTimeMillis() - wt;
							} catch (InterruptedException e) {
								// 被中断，退出尝试以及等待
								break;
							}
						}
					}
				}
				if (conn != null) {
					// 如果已经拿到connection，则返回
					if (conn.isValid()) {
						if (!conn.getRealConnection().getAutoCommit()) {
							// 连接为非自动提交事务，则将其回滚，可能存在一些未提交操作，并且防止影响下一次使用
							conn.getRealConnection().rollback();
						}
						// 根据URL,用户名以及密码计算出一个Hash，用于标识此次连接
						conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
						// 设置当前连接开始使用时间
						conn.setCheckoutTimestamp(System.currentTimeMillis());
						// 设置最后一次使用时间
						conn.setLastUsedTimestamp(System.currentTimeMillis());
						// 加入活动队列中
						state.activeConnections.add(conn);
						// 统计请求次数
						state.requestCount++;
						// 统计获取连接所需时间
						state.accumulatedRequestTime += System.currentTimeMillis() - t;
					} else {
						// 无效连接
						if (log.isDebugEnabled()) {
							log.debug("A bad connection (" + conn.getRealHashCode()
									+ ") was returned from the pool, getting another connection.");
						}
						// 统计无效连接个数
						state.badConnectionCount++;
						localBadConnectionCount++;
						conn = null;
						if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
							// 如果好几次都拿不到，就放弃了，抛出异常
							if (log.isDebugEnabled()) {
								log.debug("PooledDataSource: Could not get a good connection to the database.");
							}
							throw new SQLException(
									"PooledDataSource: Could not get a good connection to the database.");
						}
					}
				}
			}

		}
		// 从上面的循环退出，如果为null，则一定出现异常情况了
		if (conn == null) {
			if (log.isDebugEnabled()) {
				log.debug(
						"PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
			}
			throw new SQLException(
					"PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
		}

		return conn;
	}

	/**
	 * Method to check to see if a connection is still usable(检查连接是否仍然可用。)
	 *
	 * @param conn
	 *            - the connection to check
	 * 
	 * @return True if the connection is still usable
	 */
	protected boolean pingConnection(PooledConnection conn) {
		boolean result = true;

		try {
			result = !conn.getRealConnection().isClosed();
		} catch (SQLException e) {
			if (log.isDebugEnabled()) {
				log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
			}
			result = false;
		}

		if (result) {
			if (poolPingEnabled) {
				if (poolPingConnectionsNotUsedFor >= 0
						&& conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
					try {
						if (log.isDebugEnabled()) {
							log.debug("Testing connection " + conn.getRealHashCode() + " ...");
						}
						Connection realConn = conn.getRealConnection();
						Statement statement = realConn.createStatement();
						ResultSet rs = statement.executeQuery(poolPingQuery);
						rs.close();
						statement.close();
						if (!realConn.getAutoCommit()) {
							realConn.rollback();
						}
						result = true;
						if (log.isDebugEnabled()) {
							log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
						}
					} catch (Exception e) {
						log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
						try {
							conn.getRealConnection().close();
						} catch (Exception e2) {
							// ignore
						}
						result = false;
						if (log.isDebugEnabled()) {
							log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
						}
					}
				}
			}
		}
		return result;
	}

	/**
	 * Unwraps a pooled connection to get to the 'real'
	 * connection(解压缩一个合并的连接以获得“真实”的连接)
	 *
	 * @param conn
	 *            - the pooled connection to unwrap
	 * 
	 * @return The 'real' connection
	 */
	public static Connection unwrapConnection(Connection conn) {
		if (Proxy.isProxyClass(conn.getClass())) {
			InvocationHandler handler = Proxy.getInvocationHandler(conn);
			if (handler instanceof PooledConnection) {
				return ((PooledConnection) handler).getRealConnection();
			}
		}
		return conn;
	}

	/**
	 * 结束
	 */
	protected void finalize() throws Throwable {
		forceCloseAll();
		super.finalize();
	}

	/**
	 * 拆开
	 */
	public <T> T unwrap(Class<T> iface) throws SQLException {
		throw new SQLException(getClass().getName() + " is not a wrapper.");
	}

	/**
	 * 是否为包装器
	 */
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return false;
	}

	/**
	 * 得到父级日志
	 */
	public Logger getParentLogger() {
		return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); // requires JDK
															// version 1.6
	}

}
